/*******************************************************************************************************************
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 Nicolas DAURES
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *******************************************************************************************************************/


/****************************************************************
 * Renderable
 ****************************************************************/

// Renderable
function Renderable()
{
    //=======================
    // Fields
    //=======================

    this.m_SceneNode =	new SceneNode();
    this.m_iPriority = 	0;
};


//=======================
// Accessors
//=======================

Renderable.prototype.getSceneNode = function()
{
    return this.m_SceneNode;
}

Renderable.prototype.getPriority = function()
{
    return this.m_iPriority;
}

Renderable.prototype.setPriority = function(a_iPriority)
{
    this.m_iPriority = a_iPriority;
}


/****************************************************************
 * Texture
 ****************************************************************/

// Texture which contains a scene node and an image to draw
function Texture()
{
    Renderable.call(this);


    //=======================
	// Fields
	//=======================

    this.m_Image = 		null;
};
Texture.prototype = Object.create(Renderable.prototype);
Texture.prototype.constructor = Texture;


//=======================
// Accessors
//=======================

Texture.prototype.getSceneNode = function()
{
    return Renderable.prototype.getSceneNode.call(this);
}

Texture.prototype.getPriority = function()
{
    return Renderable.prototype.getPriority.call(this);
}

Texture.prototype.setPriority = function(a_iPriority)
{
    return Renderable.prototype.setPriority.call(this, a_iPriority);
}

Texture.prototype.getImage = function()
{
    return this.m_Image;
}

Texture.prototype.setImage = function(a_Image)
{
    this.m_Image = a_Image;
}


//=======================
// Operations
//=======================

Texture.prototype.draw = function()
{
    var v2CamPos = CameraEngine.getInstance().m_SceneNode.m_v2Pos;
    var v2PosInScreen = convertScenePosToScreenPos(this.m_SceneNode.m_v2WorldPos, v2CamPos);

    context.save();
    context.translate(v2PosInScreen.x, v2PosInScreen.y);
    context.rotate(this.m_SceneNode.m_fWorldOrientation);
    context.scale(this.m_SceneNode.m_v2WorldScale.x, this.m_SceneNode.m_v2WorldScale.y);
    context.drawImage(this.m_Image, -0.5, -0.5, 1, 1);
    context.restore();
}

Texture.prototype.horizontalFlip = function()
{
    this.m_SceneNode.m_v2Scale.x *= -1;
}

Texture.prototype.verticalFlip = function()
{
    this.m_SceneNode.m_v2Scale.y *= -1;
}


/****************************************************************
 * Sprite Sheet
 ****************************************************************/

// Sprite sheet (image with N sprites)
function SpriteSheet()
{
	//=======================
	// Fields
	//=======================

    this.m_Image = 		    null;
    this.m_iFrameCount = 	0;
    this.m_iLineCount = 	0;
    this.m_iColumnCount =	0;
    this.m_v2FrameSize = 	new b2Vec2();
};


//=======================
// Accessors
//=======================

SpriteSheet.prototype.getImage = function()
{
    return this.m_Image;
}

SpriteSheet.prototype.setImage = function(a_Image)
{
    this.m_Image = a_Image;
}

SpriteSheet.prototype.getFrameCount = function()
{
    return this.m_iFrameCount;
}

SpriteSheet.prototype.setFrameCount = function(a_iFrameCount)
{
    this.m_iFrameCount = a_iFrameCount;
}

SpriteSheet.prototype.getLineCount = function()
{
    return this.m_iLineCount;
}

SpriteSheet.prototype.setLineCount = function(a_iLineCount)
{
    this.m_iLineCount = a_iLineCount;
}

SpriteSheet.prototype.getColumnCount = function()
{
    return this.m_iColumnCount;
}

SpriteSheet.prototype.setColumnCount = function(a_iColumnCount)
{
    this.m_iColumnCount = a_iColumnCount;
}

SpriteSheet.prototype.getFrameSize = function()
{
    return this.m_v2FrameSize;
}

SpriteSheet.prototype.setFrameSize = function(a_v2FrameSize)
{
    this.m_v2FrameSize.x = a_v2FrameSize.x;
    this.m_v2FrameSize.y = a_v2FrameSize.y;
}


/****************************************************************
 * Animation
 ****************************************************************/

// Animation in a sprite sheet
function Animation()
{
	//=======================
	// Fields
	//=======================

    this.m_SpriteSheet =	null;
    this.m_aFrameIndexes =  new Array();
    this.m_iFrameCount = 	0;
    this.m_fDuration = 		0;
};


//=======================
// Accessors
//=======================

Animation.prototype.getSpriteSheet = function()
{
    return this.m_SpriteSheet;
}

Animation.prototype.setSpriteSheet = function(a_SpriteSheet)
{
    this.m_SpriteSheet = a_SpriteSheet;
}

Animation.prototype.getFrameIndexes = function()
{
    return this.m_aFrameIndexes;
}

Animation.prototype.setFrameIndexes = function(a_aFrameIndexes)
{
    this.m_aFrameIndexes = a_aFrameIndexes;
}

Animation.prototype.getFrameCount = function()
{
    return this.m_iFrameCount;
}

Animation.prototype.setFrameCount = function(a_iFrameCount)
{
    this.m_iFrameCount = a_iFrameCount;
}

Animation.prototype.getDuration = function()
{
    return this.m_fDuration;
}

Animation.prototype.setDuration = function(a_fDuration)
{
    this.m_fDuration = a_fDuration;
}


/****************************************************************
 * Animation Player
 ****************************************************************/

// Animation player which can read an animation in a sprite sheet
function AnimationPlayer()
{
    Renderable.call(this);


    //=======================
	// Fields
	//=======================

    this.m_SpriteSheet = 			null;
    this.m_aAnimations = 			new Array();
    this.m_iCurrentAnimation =	    0;
    this.m_fCurrentTime = 		    0;
    this.m_iCurrentFrameLine = 	    0;
    this.m_iCurrentFrameColumn =    0;
    this.m_bHFlip = 				false;
    this.m_bVFlip = 				false;
};
AnimationPlayer.prototype = Object.create(Renderable.prototype);
AnimationPlayer.prototype.constructor = AnimationPlayer;


//=======================
// Accessors
//=======================

AnimationPlayer.prototype.getSpriteSheet = function()
{
    return this.m_SpriteSheet;
}

AnimationPlayer.prototype.setSpriteSheet = function(a_SpriteSheet)
{
    this.m_SpriteSheet = a_SpriteSheet;
}

AnimationPlayer.prototype.getAnimations = function()
{
    return this.m_aAnimations;
}

AnimationPlayer.prototype.setAnimations = function(a_aAnimations)
{
    this.m_aAnimations = a_aAnimations;
}

AnimationPlayer.prototype.getCurrentAnimation = function()
{
    return this.m_iCurrentAnimation;
}

AnimationPlayer.prototype.setCurrentAnimation = function(a_iCurrentAnimation)
{
    this.m_iCurrentAnimation = a_iCurrentAnimation;
}

AnimationPlayer.prototype.getCurrentTime = function()
{
    return this.m_fCurrentTime;
}

AnimationPlayer.prototype.setCurrentTime = function(a_fCurrentTime)
{
    this.m_fCurrentTime = a_fCurrentTime;
}

AnimationPlayer.prototype.getCurrentFrameLine = function()
{
    return this.m_iCurrentFrameLine;
}

AnimationPlayer.prototype.setCurrentFrameLine = function(a_iCurrentFrameLine)
{
    this.m_iCurrentFrameLine = a_iCurrentFrameLine;
}

AnimationPlayer.prototype.getCurrentFrameColumn = function()
{
    return this.m_iCurrentFrameColumn;
}

AnimationPlayer.prototype.setCurrentFrameColumn = function(a_iCurrentFrameColumn)
{
    this.m_iCurrentFrameColumn = a_iCurrentFrameColumn;
}


//=======================
// Operations
//=======================

AnimationPlayer.prototype.draw = function()
{
    this.update();

    var v2CamPos = CameraEngine.getInstance().m_SceneNode.m_v2Pos;
    var v2PosInScreen = convertScenePosToScreenPos(this.m_SceneNode.m_v2WorldPos, v2CamPos);

    context.save();
    context.translate(v2PosInScreen.x, v2PosInScreen.y);
    context.rotate(this.m_SceneNode.m_fWorldOrientation);
    context.scale(this.m_SceneNode.m_v2WorldScale.x, this.m_SceneNode.m_v2WorldScale.y);
    context.drawImage(this.m_SpriteSheet.m_Image,
        this.m_iCurrentFrameColumn * this.m_SpriteSheet.m_v2FrameSize.x,
        this.m_iCurrentFrameLine * this.m_SpriteSheet.m_v2FrameSize.y,
        this.m_SpriteSheet.m_v2FrameSize.x, this.m_SpriteSheet.m_v2FrameSize.y,
        -0.5, -0.5,
        1, 1);
    context.restore();
}

AnimationPlayer.prototype.update = function()
{
    var animation = this.m_aAnimations[this.m_iCurrentAnimation];

    this.m_fCurrentTime += TimeEngine.getInstance().m_fDeltaTime;
    if (this.m_fCurrentTime >= animation.m_fDuration)
    {
        this.m_fCurrentTime -= animation.m_fDuration;
    }

    var v2FrameSize = this.m_SpriteSheet.m_v2FrameSize;
    var iFrameCount = animation.m_iFrameCount;
    var iFrameIndex = animation.m_aFrameIndexes[Math.floor(this.m_fCurrentTime * iFrameCount/ animation.m_fDuration)];
    this.m_iCurrentFrameLine = Math.floor(iFrameIndex / this.m_SpriteSheet.m_iColumnCount);
    this.m_iCurrentFrameColumn = iFrameIndex % this.m_SpriteSheet.m_iColumnCount;
}

AnimationPlayer.prototype.horizontalFlip = function()
{
    this.m_bHFlip = !this.m_bHFlip;
    this.m_SceneNode.m_v2Scale.x *= -1;
}

AnimationPlayer.prototype.verticalFlip = function()
{
    this.m_bVFlip = !this.m_bVFlip;
    this.m_SceneNode.m_v2Scale.y *= -1;
}


/****************************************************************
 * Text
 ****************************************************************/

// 2 dimension text
function Text2D()
{
    Renderable.call(this);


	//=======================
	// Fields
	//=======================

    this.m_Color = 		"#000000";
    this.m_Font = 		"italic small-caps bold 12px arial";
    this.m_Text = 		"Text";
};
Text2D.prototype = Object.create(Renderable.prototype);
Text2D.prototype.constructor = Text2D;


//=======================
// Accessors
//=======================

Text2D.prototype.getColor = function()
{
    return this.m_Color;
}

Text2D.prototype.setColor = function(a_Color)
{
    this.m_Color = a_Color;
}

Text2D.prototype.getFont = function()
{
    return this.m_Font;
}

Text2D.prototype.setFont = function(a_Font)
{
    this.m_Font = a_Font;
}

Text2D.prototype.getText = function()
{
    return this.m_Text;
}

Text2D.prototype.setText = function(a_Text)
{
    this.m_Text = a_Text;
}


//=======================
// Operations
//=======================

Text2D.prototype.draw = function()
{
    var v2CamPos = CameraEngine.getInstance().m_SceneNode.m_v2Pos;
    var v2PosInScreen = convertScenePosToScreenPos(this.m_SceneNode.m_v2WorldPos, v2CamPos);

    context.save();
    context.translate(v2PosInScreen.x, v2PosInScreen.y);
    context.rotate(this.m_SceneNode.m_fWorldOrientation);
    context.scale(this.m_SceneNode.m_v2WorldScale.x, this.m_SceneNode.m_v2WorldScale.y);
    context.fillStyle = this.m_Color;
    context.font = this.m_Font;
    context.fillText(this.m_Text, 0, 0);
    context.restore();
}


/****************************************************************
 * Render Engine
 ****************************************************************/

// Display textures, animations and texts
function RenderEngine()
{
	//=======================
	// Fields
	//=======================

    this.m_aRenderables =		new Array();
	this.m_ClearColor = 		"#ffffff";
	this.m_v2CanvasSize =		new b2Vec2(640, 480);
	this.m_v2CanvasHalfSize =	new b2Vec2(320, 240);
    this.m_bSortRenderables =   false;
}


//=======================
// Accessors
//=======================

RenderEngine.prototype.getClearColor = function()
{
    return this.m_ClearColor;
}

RenderEngine.prototype.setClearColor = function(a_ClearColor)
{
    this.m_ClearColor = a_ClearColor;
}

RenderEngine.prototype.getCanvasSize = function()
{
    return this.m_v2CanvasSize;
}

RenderEngine.prototype.setCanvasSize = function(a_v2CanvasSize)
{
    this.m_v2CanvasSize.x = a_v2CanvasSize.x;
    this.m_v2CanvasSize.y = a_v2CanvasSize.y;
    this.m_v2CanvasHalfSize.x = a_v2CanvasSize.x * 0.5;
    this.m_v2CanvasHalfSize.y = a_v2CanvasSize.y * 0.5;
}

RenderEngine.prototype.getCanvasHalfSize = function()
{
    return this.m_v2CanvasHalfSize;
}


//=======================
// Operations
//=======================

RenderEngine.prototype.update = function()
{
    if (this.m_bSortRenderables)
    {
        this.sort();
        this.m_bSortRenderables = false;
    }

    context.clearRect(0, 0, this.m_v2CanvasSize.x, this.m_v2CanvasSize.y);

    context.fillStyle = this.m_ClearColor;
    context.fillRect(0, 0, this.m_v2CanvasSize.x, this.m_v2CanvasSize.y);

    // Display renderables
    for (var i = 0; i < this.m_aRenderables.length; i++)
    {
        this.m_aRenderables[i].draw();
    }
}

RenderEngine.prototype.addRenderable = function(a_Renderable)
{
    this.m_aRenderables.push(a_Renderable);
    this.m_bSortRenderables = true;
}

RenderEngine.prototype.removeRenderable = function(a_Renderable)
{
    for (var i = 0; i < this.m_aRenderables.length; i++)
    {
        if (this.m_aRenderables[i] == a_Renderable)
        {
            this.m_aRenderables.splice(i, 1);
            return;
        }
    }
}

RenderEngine.prototype.sort = function()
{
    this.m_aRenderables.sort(function sortRenderableFunction(a_Renderable1, a_Renderable2)
    {
        if (a_Renderable1.getPriority() > a_Renderable2.getPriority()) return 1;
        if (a_Renderable1.getPriority() < a_Renderable2.getPriority()) return -1;
        return 0;
    });
}

RenderEngine.m_Instance = null;
RenderEngine.getInstance = function()
{  
    if (this.m_Instance == null)
    {  
		this.m_Instance = new RenderEngine();  
    }  

    return this.m_Instance;  
}


/****************************************************************
 * Tool functions
 ****************************************************************/

// Convert a scene to position to screen position
function convertScenePosToScreenPos(a_v2ScenePos, a_v2CamPos)
{
	var v2CanvasHalfSize = RenderEngine.getInstance().getCanvasHalfSize();
	return new b2Vec2(a_v2ScenePos.x - a_v2CamPos.x + v2CanvasHalfSize.x, -(a_v2ScenePos.y - a_v2CamPos.y) + v2CanvasHalfSize.y);
}